package com.spring.demo.init.db;

import java.util.List;
import java.util.Properties;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import com.spring.demo.core.dao.IMemberDao;
import com.spring.demo.core.entity.Member;
import com.spring.demo.core.entity.SomeItem;
import com.spring.demo.core.event.MemberSaveEmail;
import com.spring.demo.core.event.MemberSaveEmailSender;
import com.spring.demo.core.service.IMemberService;
import com.spring.demo.core.service.ISomeItemService;

@Configurable
@EnableTransactionManagement
@EnableJpaRepositories(basePackageClasses = { SomeItemRespository.class })
@PropertySource({ "classpath:db/jdbcTemplate.properties" })
public class JpaConfig extends EmbeddedDataSource {

	// hibernate properties
	@Value("${hibernate.dialect}")
	private String HIBERNATE_DIALECT;

	@Value("${hibernate.show_sql}")
	private String HIBERNATE_SHOW_SQL;

	@Value("${hibernate.hbm2ddl.auto}")
	private String HIBERNATE_HBM2DDL_AUTO;

	@Bean
	@Autowired
	public LocalContainerEntityManagerFactoryBean entityManagerFactory(
			DataSource dataSource) {
		LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
		entityManagerFactoryBean.setDataSource(dataSource);
		entityManagerFactoryBean
				.setPackagesToScan("com.spring.demo.core.entity");

		JpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
		entityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter);
		entityManagerFactoryBean.setJpaProperties(hibernateProperties());

		return entityManagerFactoryBean;
	}

	@Bean
	@Autowired
	public PlatformTransactionManager transactionManager(
			LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
		JpaTransactionManager transactionManager = new JpaTransactionManager(
				entityManagerFactoryBean.getObject());

		return transactionManager;
	}

	@Bean
	public PersistenceExceptionTranslationPostProcessor translationPostProcessor() {
		return new PersistenceExceptionTranslationPostProcessor();
	}

	@Bean
	public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
		return new PropertySourcesPlaceholderConfigurer();
	}

	@Bean
	public IMemberService memberService() {
		return new MemberService();
	}

	@Bean
	public IMemberDao memberDao() {
		return new MemberDao();
	}

	@Bean
	public ISomeItemService someItemService() {
		return new SomeItemService();
	}

	private Properties hibernateProperties() {
		Properties prop = new Properties();
		prop.setProperty("hibernate.dialect", HIBERNATE_DIALECT);
		prop.setProperty("hibernate.show_sql", HIBERNATE_SHOW_SQL);
		prop.setProperty("hibernate.hbm2ddl.auto", HIBERNATE_HBM2DDL_AUTO);

		return prop;
	}

	@Bean
	public MemberSaveEmailSender saveEmailSender() {
		return new MemberSaveEmailSender();
	}

	@Transactional
	static class MemberService implements IMemberService,
			ApplicationEventPublisherAware {
		@Autowired
		private IMemberDao memberDao;
		private ApplicationEventPublisher publisher;

		/**
		 * 事务提交后发送邮件
		 */
		@Override
		public void saveMember(final Member member) {
			memberDao.saveMember(member);
			if (TransactionSynchronizationManager.isSynchronizationActive()) {
				TransactionSynchronizationManager
						.registerSynchronization(new TransactionSynchronizationAdapter() {
							@Override
							public void afterCommit() {
								publisher.publishEvent(new MemberSaveEmail(
										member));
							}
						});
			}
		}

		@Override
		public Member getMemberById(Long id) {
			return memberDao.getMemberById(id);
		}

		@Override
		public List<Member> getAllMembers() {
			return memberDao.getAllMembers();
		}

		@Override
		public void setApplicationEventPublisher(
				ApplicationEventPublisher applicationEventPublisher) {
			this.publisher = applicationEventPublisher;
		}
	}

	static class MemberDao implements IMemberDao {

		@PersistenceContext
		private EntityManager entityManager;

		@Override
		public void saveMember(Member member) {
			entityManager.persist(member);
			if ("张三".equals(member.getName())) {
				throw new RuntimeException("阻止事务提交");
			}
		}

		@Override
		public Member getMemberById(Long id) {
			return entityManager.find(Member.class, id);
		}

		@Override
		public List<Member> getAllMembers() {
			return entityManager.createQuery("from Member", Member.class)
					.getResultList();
		}
	}

	@Transactional
	static class SomeItemService implements ISomeItemService {
		@Autowired
		private SomeItemRespository someItemRespository;

		@PostConstruct
		public void postConstruct() {
			SomeItem someItem = new SomeItem();
			someItem.setSomeText("aaa");
			someItemRespository.saveAndFlush(someItem);

			someItem = new SomeItem();
			someItem.setSomeText("bbb");
			someItemRespository.saveAndFlush(someItem);

			someItem = new SomeItem();
			someItem.setSomeText("ccc");
			someItemRespository.saveAndFlush(someItem);
		}

		@Override
		public List<SomeItem> getAll() {
			return someItemRespository.findAll();
		}

		@Override
		public SomeItem save(SomeItem someItem) {
			return someItemRespository.saveAndFlush(someItem);
		}

		@Override
		public boolean delete(Long id) {
			someItemRespository.delete(id);
			return true;
		}
	}
}

interface SomeItemRespository extends JpaRepository<SomeItem, Long> {

}
